1.5 遍历字符
- 问题:你想遍历字符串中的每个字符,在遍历字符串时对每个字符进行操作。
1.5.1 解决方案:
根据你的需求和喜好,你可以使用map或者foreach方法,一个是循环,另一个是其他方法。
map把输入字符转成大写的例子
scala> val upper = "hello, world".map(c => c.toUpper) upper: String = HELLO, WORLD //大部分使用以下缩写,使用下划线的魔法 scala> val upper = "hello, world".map(_.toUpper) upper: String = HELLO, WORLD
任何集合中,如字符串的字符序列,你可以把集合方法链接在一起以达到预期的结果。下面的例子是在原始字符串的基础上调用filter方法将所有的小写字母“L”删除后创建一个新的字符串,然后已这个新的字符串作为ma方法的输入将剩余的字符转换成大写字符。
scala> val upper = "hello, world".filter(_ != 'l').map(_.toUpper) upper: String = HEO, WORD
当你刚开始使用Scala时可能不大习惯map方法,使用Scala的for循环可以达到相同的结果。
scala> for (c <- "hello") println(c) h e l l o
要写一个和map方法一样的for循环,可以在循环结束添加一个yield,此时的for/yield循环和前两个map方法例子是一样的。
scala> val upper = for (c <- "hello, world") yield c.toUpper upper: String = HELLO, WORLD
在for循环中添加yield本质上是将每次循环结果放置到暂存区,当循环结果,所有在暂存区的元素将作为一个集合返回。下例for/yield循环结果和第三个map方法一样。
val result = for { c <- "hello, world" if c != 'l' } yield c.toUppe
map方法和for/yield方法用于将一个集合转换成另一个,而foreach方法通常用来操作每个元素而且没有返回值,下面这个打印的情况比较有用。
scala> "hello".foreach(println) h e l l o
1.5.2 讨论
因为Scala把string当做字符序列,并且Scala是面向对象和函数式编程语言,你可以如上述方法显示的一样遍历字符串中的字符,下面使用经典Java方法进行比较。
String s = "Hello"; StringBuilder sb = new StringBuilder(); for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); // do something with the character ... // sb.append ... } String result = sb.toString();
可以看到Scala方法更加简洁并且还是很可读的,这种简洁和可读使你更加专注于解决手上的问题,一旦你熟悉Scala,就像Java例子中的命令代码掩盖了业务逻辑。
维基百科中对命令式编程的描述是这样的:命令式编程是一种编程范式,在改变程序状态的一系列声明中描述计算。命令程序定义计算机的命令序列来执行。这是Java的例子所示,它定义了一系列明确的声明告诉计算机如何达到预期的结果。
1.5.3 理解map如何工作:
根据你的编码偏好,你可以在map方法中使用大的代码块。这两个例子证明了通过一个运算来传递一个map方法的语法。
// first example "HELLO".map(c => (c.toByte+32).toChar) // second example "HELLO".map{ c => (c.toByte+32).toChar }
注意算法一次操作一个字符。因为这个例子中map方法被字符串调用,map将字符串当做一个字符元素的有序集合。map方法有一个隐式循环,并且在循环中一次操作一个字符运算。
尽管这个运算很短,设想一下它需要很长的时间。在这种情况下,为了保持你的代码清晰,你可能想要把它写成一个方法(或函数),这个方法可以传递到map方法。要编写一个可以传递到map方法执行字符串中的字符的方法,定义它以一个单一的字符作为输入,然后执行该方法中的字符的逻辑。当逻辑完成时,返回任何运算返回的结果。尽管下面的运算依然很短,它演示了如何创建一个自定义方法,并将这个方法传递到map中。
// 自定义操作字符的方法 scala> def toLower(c: Char): Char = (c.toByte+32).toChar toLower: (c: Char)Char // 使用map方法 scala> "HELLO".map(toLower) res0: String = hello
作为额外的好处,同样的方法也可以使用在for/yield方法中。
scala> val s = "HELLO" s: java.lang.String = HELLO scala> for (c <- s) yield toLower(c) res1: String = hello
在讨论中我使用了“方法”一词,但是你也可以在这里使用函数代替方法,那么函数和方法的不同是什么?
下面这个函数相当于toLower方法
val toLower = (c: Char) => (c.toByte+32).toChar toLower: Char => Char = <function1>
这个函数可以和之前使用的toLower方法以同样的方式传递到map中
scala> "HELLO".map(toLower) res0: String = hello
- 详情可看第九章函数编程,查看更多函数信息和方法函数区别的。
1.5.4 完整的例子
下面的例子演示如何在字符串中调用getBytes方法,然后传递代码块到foreach方法中来计算一个字符串的Adler-32校验和值。
package tests /** * Calculate the Adler-32 checksum using Scala. * @see http://en.wikipedia.org/wiki/Adler-32 */ object Adler32Checksum { val MOD_ADLER = 65521 def main(args: Array[String]) { val sum = adler32sum("Wikipedia") printf("checksum (int) = %d\n", sum) printf("checksum (hex) = %s\n", sum.toHexString) } def adler32sum(s: String): Int = { var a = 1 var b = 0 s.getBytes.foreach{char => a = (char + a) % MOD_ADLER b = (b + a) % MOD_ADLER } // note: Int is 32 bits, which this requires b * 65536 + a // or (b << 16) + a } }
getBytes方法返回一个字符串的字节有序集合,如下所示:
scala> "hello".getBytes res0: Array[Byte] = Array(104, 101, 108, 108, 111)
调用getBytes之后添加foreach方法可以操作每个字节的值:
scala> "hello".getBytes.foreach(println) 104 101 108 108 111
上面例子中使用foreach代替map,因为目标是循环字符串中的每个字节,并且在每个字节上进行一些操作,但是你并不想在循环的最后返回任何值。
查看更多
1.6 字符串中的搜索模式
- 问题:确定一个字符串中是否包含正则表达式模式
1.6.1 解决方案
- 通过在字符串上调用.r方法创建一个正则表达式对象,当你查找一个匹配时使用findFirstIn模式,搜索所有匹配时用findAllIn。
首先创建一个你想搜索的正则表达式模式,下面例子中是一个或多个数字字符的序列。
//创建规则 scala> val numPattern = "[0-9]+".r numPattern: scala.util.matching.Regex = [0-9]+ //创建需要查找的字符串例子 scala> val address = "123 Main Street Suite 101" address: java.lang.String = 123 Main Street Suite 101 //使用findFirstIn方法查找第一个匹配(返回的是Option[String],讨论中详解) scala> val match1 = numPattern.findFirstIn(address) match1: Option[String] = Some(123) //使用findAllIn查找复杂匹配,返回迭代器 scala> val matches = numPattern.findAllIn(address) matches: scala.util.matching.Regex.MatchIterator = non-empty iterator //循环输出结果 scala> matches.foreach(println) 123 101
如果findAllIn查找不到任何结果,就会返回一个空的迭代器,你不需要检查结果是否为空,所以你可以和之前一样写代码。如果你需要把结果输出成Array数组,可以再findAllIn之后调用toArray方法。
scala> val matches = numPattern.findAllIn(address).toArray matches: Array[String] = Array(123, 101)
如果没有匹配结果,这个方法会yield一个空的Array数组,其他方法如toList,toSeq,toVector也可用。
1.6.2 讨论
在字符串之后使用.r方法比创建一个正则表达式对象要更加简单。另一个方法是导入Regex类,创建正则实例,然后如下方式使用实例:
scala> import scala.util.matching.Regex import scala.util.matching.Regex scala> val numPattern = new Regex("[0-9]+") numPattern: scala.util.matching.Regex = [0-9]+ scala> val address = "123 Main Street Suite 101" address: java.lang.String = 123 Main Street Suite 101 scala> val match1 = numPattern.findFirstIn(address) match1: Option[String] = Some(123)
尽管稍显繁琐,不过更加明显易懂。我发现很容易在字符串的后面忽略.r(然后花了几分钟想知道我看到的代码如何工作)
1.6.3 处理findFirstIn返回的Option
如在解决方案中提到过的,findFirstIn方法查找字符串中的第一个匹配然后返回一个Option[String]:
scala> val match1 = numPattern.findFirstIn(address) match1: Option[String] = Some(123)
在章节20.6会详细说明Option/Some/None模式,简单的理解Option就是它是保存0个或1个值的容器。在这个findFirstIn的例子中,如果成功会返回字符串“123”如Some(123),如果查找失败会返回None:
scala> val address = "No address given" address: String = No address given scala> val match1 = numPattern.findFirstIn(address) match1: Option[String] = None
总结就是,一个定义的方法返回的Option[String]要不返回的是Some(String),要么是None。正常使用Option的方式一般如下:
- 在结果后调用getOrElse
- 在匹配表达式中使用Option
- 在foreach循环中使用Option
章节20.6会详细描述这些方法,为了方便这里会简单介绍下。
使用getOrElse方法,会试图“get”到结果,同时还指定当方法失败时应使用的默认值:
scala> val result = numPattern.findFirstIn(address).getOrElse("no match") result: String = 123
因为Option是0个或1个元素的集合,一个有经验的Scala开发者会在这种情况下同时使用foreach循环。
numPattern.findFirstIn(address).foreach { e => // perform the next step in your algorithm, // operating on the value 'e' }
匹配表达式也为这个问题提供了一个非常可读的解决方案,更多查看20.6章。
match1 match { case Some(s) => println(s"Found: $s") case None => }
对这个方法进行总结,下面的REPL例子显示了创建正则表达式,通过findFirstIn搜索字符串,在匹配结果使用foreach循环的完整结果。
scala> val numPattern = "[0-9]+".r numPattern: scala.util.matching.Regex = [0-9]+ scala> val address = "123 Main Street Suite 101" address: String = 123 Main Street Suite 101 scala> val match1 = numPattern.findFirstIn(address) match1: Option[String] = Some(123) scala> match1.foreach { e => | println(s"Found a match: $e") | } Found a match: 123
查看更多
1.7 字符串中的替换模式
- 问题:想要在字符串中搜索一个正则表达式模式并替换他们。
1.7.1 解决方案
- 因为字符串是不可改变的,你不能直接在字符串的基础上进行查找并替换的操作。但是你可以创建一个包含被替换内容的新字符串。有以下几种方式做到这个。
可以在字符串上调用replaceAll,记住将结果分配给一个新的变量。
scala> val address = "123 Main Street".replaceAll("[0-9]", "x") address: java.lang.String = xxx Main Street
可以创建正则表达式然后在表达式基础上调用replaceAllIn,仍然需要记住将结果分配给一个新的变量。
scala> val regex = "[0-9]".r regex: scala.util.matching.Regex = [0-9] scala> val newAddress = regex.replaceAllIn("123 Main Street", "x") newAddress: String = xxx Main Street
只替换第一个匹配模式的,使用replaceFirst方法:
scala> val result = "123".replaceFirst("[0-9]", "x") result: java.lang.String = x23
也可以使用一个正则表达式的replaceFirstIn:
scala> val regex = "H".r regex: scala.util.matching.Regex = H scala> val result = regex.replaceFirstIn("Hello world", "J") result: String = Jello world